/**
 * Copyright (C) 2010 - 2013 Harry Glasgow
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.jaden.engine;

import com.googlecode.jaden.common.config.IOConfiguration;
import com.googlecode.jaden.common.other.JadenException;

public class Model {

    private final IOUnit[] inputUnits;
    private boolean[] inputsMask;
    private final Network network;
    private final IOUnit outputUnit;

    public Model(IOConfiguration[] inputConfigurations, boolean[] inputsMask, Network network,
                 IOConfiguration outputConfiguration) throws JadenException {
        this(inputConfigurations, network, outputConfiguration);
        if (inputsMask.length != inputConfigurations.length) {
            throw new JadenException("InputsMask width (" + inputsMask.length +
                    ") is not the same as the inputConfiguration width (" + inputConfigurations.length + ").");
        }
        this.inputsMask = inputsMask;
    }

    public Model(IOConfiguration[] inputConfigurations, Network network, IOConfiguration outputConfiguration) {

        // Default a full inputs mask.
        inputsMask = new boolean[inputConfigurations.length];
        for (int i = 0; i < inputsMask.length; i++) {
            inputsMask[i] = true;
        }

        inputUnits = new IOUnit[inputConfigurations.length];
        for (int i = 0; i < inputConfigurations.length; i++) {
            inputUnits[i] = new IOUnit(inputConfigurations[i]);
        }

        outputUnit = new IOUnit(outputConfiguration);

        int count = 0;
        for (IOUnit inputUnit : inputUnits) {
            count += inputUnit.getInternalWidth();
        }
        if (network.getInputWidth() != count) {
            throw new IllegalStateException("Input mismatch " + network.getInputWidth() + ' ' + count);
        }
        if (network.getOutputWidth() != outputUnit.getInternalWidth()) {
            throw new IllegalStateException("Output mismatch " + network.getOutputWidth() + ' ' +
                    outputUnit.getInternalWidth());
        }
        this.network = network;
    }

    public String forward(String[] inputs) throws JadenException {
        if (inputUnits.length != inputsMask.length) {
            throw new IllegalStateException("Bad width " + inputUnits.length + ' ' + inputsMask.length);
        }
        double[] inputSet = new double[network.getInputWidth()];
        int i = 0;
        int pos = 0;
        int inputUnitPointer = 0;
        for (boolean inputMask : inputsMask) {
            double[] doubles = inputUnits[inputUnitPointer].toInternal(inputs[i++]);
            if (inputMask) {
                System.arraycopy(doubles, 0, inputSet, pos, doubles.length);
            }
            pos += doubles.length;
            inputUnitPointer++;
        }
        double[] doubles = network.forward(inputSet);
        return outputUnit.toExternal(doubles);
    }

    public void backwards(String target, String actual) throws JadenException {
        double[] internalTarget = outputUnit.toInternal(target);
        double[] internalActual = outputUnit.toInternal(actual);
        double[] doubles = outputUnit.toError(internalTarget, internalActual);
        network.backward(doubles);
    }

    public boolean isCategoricalOutputType() {
        return outputUnit.isCategoricalOutputType();
    }

    public void jog() {
        network.jog();
    }
}
